﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Net;
using System.Net.Sockets;

namespace NickPowell.LiveCode
{
    public class IOHandler
    {
        private const string S_SERVER_IP_ADDRESS = "136.165.66.97"; //"127.0.0.1";//"136.165.66.97"; //"127.0.0.1";
        private const int I_SERVER_PORT = 8221;

        #region Sockets and socket information
        private IPEndPoint ipServerAddress;
        private Socket socServer;

        private List<Socket> lstSlowSendSockets = new List<Socket>();
        private int iSlowSendSocketIndex = 0;

        private List<Socket> lstQuickSendSockets = new List<Socket>();
        private int iQuickSendSocketIndex = 0;

        private List<Socket> lstSlowRecieveSockets = new List<Socket>();
        private List<Socket> lstQuickRecieveSockets = new List<Socket>();
        #endregion

        private AsyncCallback asynUserConnectCallback;

        public bool bIsConnected
        {
            get
            {
                if (this.socServer == null)
                {
                    return false;
                }

                return this.socServer.Connected;
            }
        }

        public IOHandler(AsyncCallback asynConnectCallback)
        {
            this.asynUserConnectCallback = asynConnectCallback;
        }

        /// <summary>
        /// Connect to server on default port
        /// </summary>
        public void ConnectToServer()
        {
            try
            {
                IPAddress serverAddress = IPAddress.Parse(S_SERVER_IP_ADDRESS);
                this.ipServerAddress = new IPEndPoint(serverAddress, I_SERVER_PORT);

                this.socServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);

                //Connect to the server
                this.socServer.Connect(this.ipServerAddress);
            }
            catch (SocketException socExp)
            {
                throw socExp;
            }
            catch (Exception e)
            {
                //Log and handle error
                throw e;
            }
        }

        /// <summary>
        /// Closes all socket connections
        /// </summary>
        public void DisconnectFromServer()
        {
            XmlDocument xmlLogoff = new XmlDocument();
            XmlNode xmlLiveCode = xmlLogoff.AppendChild(xmlLogoff.CreateElement("LiveCode"));
            xmlLiveCode.AppendChild(xmlLogoff.CreateElement("Logout"));

            this.SlowSend(xmlLogoff);

            foreach (Socket soc in this.lstQuickRecieveSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            foreach (Socket soc in this.lstSlowRecieveSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            foreach (Socket soc in this.lstQuickSendSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            foreach (Socket soc in this.lstSlowSendSockets)
            {
                if (soc.Connected)
                {
                    soc.Shutdown(SocketShutdown.Both);
                    soc.Close();
                }
            }

            this.lstQuickRecieveSockets.Clear();
            this.lstSlowRecieveSockets.Clear();
            this.lstQuickSendSockets.Clear();
            this.lstSlowSendSockets.Clear();
        }

        public void SetSockets(List<int> lstSlowSendPorts, List<int> lstSlowRecievePorts, List<int> lstQuickSendPorts, List<int> lstQuickRecievePorts)
        {
            try
            {
                this.StartQuickPortsListening(lstQuickRecievePorts);
                this.StartSlowPortsListening(lstSlowRecievePorts);

                this.lstSlowSendSockets.Clear();
                this.lstQuickSendSockets.Clear();

                foreach (int port in lstSlowSendPorts)
                {
                    this.lstSlowSendSockets.Add(this.ConnectToPort(port));
                }
                foreach (int port in lstQuickSendPorts)
                {
                    this.lstQuickSendSockets.Add(this.ConnectToPort(port));
                }

                this.SendReadyForConnect();
                //After you have connected to the server, the default connection is no longer needed.
                this.socServer.Shutdown(SocketShutdown.Both);
                this.socServer.Close();
            }
            catch (Exception e)
            {
                throw e;
            }
        }

        #region Start Listening
        private void StartQuickPortsListening(List<int> lstQuickRecievePorts)
        {
            try
            {
                for(int i = 0; i < lstQuickRecievePorts.Count; i++)
                {
                    int port = lstQuickRecievePorts[i];

                    IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);

                    Socket socNewListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                    socNewListener.Bind(ipLocal);

                    this.lstQuickRecieveSockets.Add(socNewListener);

                    SocketInfo socketInfo = new SocketInfo(socNewListener, SocketInfo.SocketTypes.QuickRecieve, i);

                    try
                    {
                        //Listen for incomming connections
                        socNewListener.Listen(1);
                        //Create the callback for any client connections
                        socNewListener.BeginAccept(new AsyncCallback(this.asynUserConnectCallback), socketInfo);
                    }
                    catch (SocketException sExp)
                    {
                        //Log and handle error
                        throw sExp;
                    }
                }
            }
            catch (Exception e)
            {
                //Log and handle error
                throw e;
            }
        }

        private void StartSlowPortsListening(List<int> lstSlowRecievePorts)
        {
            try
            {
                for (int i = 0; i < lstSlowRecievePorts.Count; i++)
                {
                    int port = lstSlowRecievePorts[i];

                    IPEndPoint ipLocal = new IPEndPoint(IPAddress.Any, port);

                    Socket socNewListener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
                    socNewListener.Bind(ipLocal);

                    this.lstSlowRecieveSockets.Add(socNewListener);

                    SocketInfo socketInfo = new SocketInfo(socNewListener, SocketInfo.SocketTypes.SlowRecieve, i);

                    try
                    {
                        //Listen for incomming connections
                        socNewListener.Listen(1);
                        //Create the callback for any client connections
                        socNewListener.BeginAccept(new AsyncCallback(this.asynUserConnectCallback), socketInfo);
                    }
                    catch (SocketException sExp)
                    {
                        //Log and handle error
                        throw sExp;
                    }
                }
            }
            catch (Exception e)
            {
                //Log and handle error
                throw e;
            }
        }
        #endregion

        private Socket ConnectToPort(int iPort)
        {
            try
            {
                IPAddress serverAddress = IPAddress.Parse(S_SERVER_IP_ADDRESS);
                IPEndPoint ipServerAddress = new IPEndPoint(serverAddress, iPort);

                Socket socNewSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);

                try
                {
                    //Connect to the server
                    socNewSocket.Connect(ipServerAddress);
                }
                catch (SocketException sExp)
                {
                    //Log and handle error
                    throw sExp;
                }

                return socNewSocket;
            }
            catch(Exception e)
            {
                throw e;
            }
        }

        private void SendReadyForConnect()
        {
            XmlDocument xmlConnect = new XmlDocument();
            XmlNode xmlLiveCode = xmlConnect.AppendChild(xmlConnect.CreateElement("LiveCode"));
            XmlNode xmlPortsNode = xmlLiveCode.AppendChild(xmlConnect.CreateElement("Ports"));
            XmlNode xmlReadyForConnect = xmlPortsNode.AppendChild(xmlConnect.CreateElement("ReadyForConnect"));

            this.SlowSend(xmlConnect);
        }

        #region Send
        /// <summary>
        /// Sends the input xml document to the server, waits for a response, and returns the server's response xml document
        /// </summary>
        /// <param name="xmlServerQuery">Xml document to send to the server for a response</param>
        /// <returns>Returns the server's xml document response</returns>
        public XmlDocument SyncSendRecieve(XmlDocument xmlServerQuery)
        {
            try
            {
                if (!this.bIsConnected)
                {
                    this.ConnectToServer();
                }

                string sInputXMLDoc = xmlServerQuery.InnerXml;
                byte[] bytData = System.Text.Encoding.ASCII.GetBytes(sInputXMLDoc);

                this.socServer.Send(bytData);

                byte[] bytRecievedData = new byte[2048];

                this.socServer.Receive(bytRecievedData);

                string sOutputXMLDoc = System.Text.Encoding.ASCII.GetString(bytRecievedData);

                XmlDocument xmlServerResponse = new XmlDocument();
                xmlServerResponse.LoadXml(sOutputXMLDoc);

                return xmlServerResponse;
            }
            catch (SocketException socExp)
            {
                throw socExp;
            }
            catch (Exception e)
            {
                //Log and handle error
                throw e;
            }
        }

        public void SlowSend(XmlDocument xmlDoc)
        {
            try
            {
                if (this.lstSlowSendSockets.Count > 0)
                {
                    byte[] bytData = System.Text.Encoding.ASCII.GetBytes(xmlDoc.InnerXml);

                    if (this.iSlowSendSocketIndex == this.lstSlowSendSockets.Count - 1)
                    {
                        this.iSlowSendSocketIndex = 0;
                    }
                    else
                    {
                        this.iSlowSendSocketIndex++;
                    }

                    this.lstSlowSendSockets[this.iSlowSendSocketIndex].Send(bytData);
                }
            }
            catch (SocketException sExp)
            {
                //Log and handle error
                throw sExp;
            }
        }

        public void QuickSend(XmlDocument xmlDoc)
        {
            try
            {
                if (this.lstQuickSendSockets.Count > 0)
                {
                    byte[] bytData = System.Text.Encoding.ASCII.GetBytes(xmlDoc.InnerXml);

                    //TODO Need to find a faster way to do this and need to lock this value until done
                    if (this.iQuickSendSocketIndex == this.lstQuickSendSockets.Count - 1)
                    {
                        this.iQuickSendSocketIndex = 0;
                    }
                    else
                    {
                        this.iQuickSendSocketIndex++;
                    }

                    this.lstQuickSendSockets[this.iQuickSendSocketIndex].Send(bytData);
                }
            }
            catch (SocketException sExp)
            {
                //Log and handle error
                throw sExp;
            }
        }
        #endregion
    }
}
